package com.ruoyi.workflow.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.exception.GlobalException;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.service.ISysOssService;
import com.ruoyi.workflow.common.constant.FaceConstant;
import com.ruoyi.workflow.domain.OaFace;
import com.ruoyi.workflow.mapper.OaFaceMapper;
import com.ruoyi.workflow.service.IFaceRecognitionService;
import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @描述 人脸识别相关接口service实现类
 * @版本 v1
 * @作者 wangxuqing
 * @日期 2023-07-26 09:24
 */
@Service
public class FaceRecognitionServiceImpl implements IFaceRecognitionService {
    @Resource
    private OaFaceMapper oaFaceMapper;

    @Autowired
    private RestTemplate restTemplate;


    @Autowired
    private ISysOssService iSysOssService;

    @Override
    public JSONObject createFaceSet(String outerId) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.CREATE_FACE_SET_URL, request, String.class));
    }

    @Override
    public JSONObject getAllFaceSets() {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.GET_FACE_SETS_URL, request, String.class));
    }

    @Override
    public JSONObject getDetailOfFaceSet(String outerId, String faceSetToken, Integer start) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        if (!params.containsKey("outer_id")) {
            params.add("faceset_token", faceSetToken);
        }
        if (start != null) {
            params.add("start", start);
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.GET_DETAIL_OF_FACE_SET_URL, request, String.class));
    }

    @Override
    public JSONObject deleteFaceSet(String outerId, String faceSetToken, Integer checkEmpty) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        if (!params.containsKey("outer_id")) {
            params.add("faceset_token", faceSetToken);
        }
        if (checkEmpty != null) {
            params.add("check_empty", checkEmpty);
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        JSONObject result = JSONObject.parseObject(restTemplate.postForObject(FaceConstant.DELETE_FACE_SET_URL, request, String.class));
        if (outerId != null) {
            oaFaceMapper.deleteByOuterId(outerId);
        } else {
            oaFaceMapper.deleteByFaceSetToken(faceSetToken);
        }
        return result;
    }

    @Override
    public JSONObject addFace(String outerId, String faceSetToken, String faceToken, String userId, String attachment) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        if (!params.containsKey("outer_id")) {
            params.add("faceset_token", faceSetToken);
        }
        params.add("face_tokens", faceToken);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        JSONObject result = JSONObject.parseObject(restTemplate.postForObject(FaceConstant.ADD_FACE_URL, request, String.class));
        if (result.containsKey("error_message")) {
            throw new GlobalException("添加face_token为" + faceToken + "的人脸失败");
        }
        JSONObject setUserIdJson = setUserId(faceToken, userId);
        if (setUserIdJson.containsKey("error_message")) {
            throw new GlobalException("设置face_token为" + faceToken + "对应的user_id失败");
        }
        result.put("user_id", setUserIdJson.getString("user_id"));
        OaFace oaFace = new OaFace();
        oaFace.setFaceToken(faceToken);
        oaFace.setUserId(Long.valueOf(userId));
        oaFace.setFaceSetToken(result.getString("faceset_token"));
        oaFace.setOuterId(result.getString("outer_id"));
        oaFace.setAttachment(attachment);
        oaFaceMapper.insertSelective(oaFace);
        return result;
    }

    @Override
    @SneakyThrows
    public Map addFaceByZip(MultipartFile file, String outerId, String faceSetToken) {
        List<Map> notUploadedList = new ArrayList<>();
        List<String> uploadedList = new ArrayList<>();
        List<OaFace> oaFaceList = new ArrayList<>();
        // 对压缩包进行解压,获取可添加进人脸库的人脸
        ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream());
        ZipEntry entry = zipInputStream.getNextEntry();
        if (entry == null) {
            throw new GlobalException("只支持ZIP类型的压缩包");
        }
        while (entry != null) {
            String entryName = entry.getName();
            int suffixIndex = entryName.lastIndexOf(".");
            if (suffixIndex == -1) {
                throw new GlobalException("图片格式错误，仅支持jpg、jpeg、png格式");
            }
            String suffix = entryName.substring(suffixIndex + 1);
            String userIdStr = entryName.substring(0, suffixIndex);
            if (!"jpg".equals(suffix) && !"jpeg".equals(suffix) && !"png".equals(suffix)) {
                throw new GlobalException("图片格式错误，仅支持jpg、jpeg、png格式");
            }
            // 将图片转成字节流，并且进行人脸检测
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = zipInputStream.read(buffer)) > 0) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byte[] imageBytes = byteArrayOutputStream.toByteArray();
            JSONObject detectResult = detect(imageBytes);
            if (detectResult.getString("error_message") != null) {
                throw new GlobalException(detectResult.getString("error_message"));
            }

            // 人脸检测的结果进行分析
            Integer faceNum = detectResult.getInteger("face_num");
            if (faceNum == 0) {
                Map map = new HashMap();
                map.put("image", entryName);
                map.put("reason", "未检测到人脸");
                notUploadedList.add(map);
            }
            if (faceNum > 1) {
                Map map = new HashMap();
                map.put("image", entryName);
                map.put("reason", "检测到不止一张人脸");
                notUploadedList.add(map);
            }

            // 进行照片的上传
            File tempFile = File.createTempFile("temp", ".tmp");
            tempFile.deleteOnExit();
            FileOutputStream fos = new FileOutputStream(tempFile);
            fos.write(imageBytes);
            MultipartFile image = new MockMultipartFile(
                entry.getName(),
                entry.getName(),
                null,
                new FileInputStream(tempFile)
            );

            SysOssVo upload = iSysOssService.upload(image);

            // 组装该人脸的信息
            JSONObject face = detectResult.getJSONArray("faces").getJSONObject(0);
            String faceToken = face.getString("face_token");
            OaFace oaFace = new OaFace();
            oaFace.setFaceToken(faceToken);
            oaFace.setUserId(Long.valueOf(userIdStr));
            oaFace.setFaceSetToken(faceSetToken);
            oaFace.setOuterId(outerId);
            if (ObjectUtil.isNotEmpty(upload)){
                oaFace.setAttachment(upload.getOssId().toString());
            }
            oaFaceList.add(oaFace);
            byteArrayOutputStream.close();
//            saveUnzippedFile(entryName, zipInputStream, saveDirectory);
            zipInputStream.closeEntry();
            entry = zipInputStream.getNextEntry();
        }

        zipInputStream.close();

        // 对解压后的图片依次添加进人脸库
        for (OaFace oaFace : oaFaceList) {
            addFace(oaFace.getOuterId(), oaFace.getFaceSetToken(), oaFace.getFaceToken(), String.valueOf(oaFace.getUserId()), oaFace.getAttachment());
            uploadedList.add(String.valueOf(oaFace.getUserId()));
        }

        Map result = new HashMap();
        result.put("notUploaded", notUploadedList);
        result.put("uploaded", uploadedList);
        return result;
    }

    @Override
    public JSONObject removeFaceByFaceToken(String outerId, String faceSetToken, String faceTokens) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        if (!params.containsKey("outer_id")) {
            params.add("faceset_token", faceSetToken);
        }
        params.add("face_tokens", faceTokens);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        JSONObject result = JSONObject.parseObject(restTemplate.postForObject(FaceConstant.REMOVE_FACE_URL, request, String.class));
        List<String> faceTokenList = faceTokensToList(faceTokens);
        oaFaceMapper.deleteByFaceTokenList(faceTokenList);
        return result;
    }

    @Override
    public JSONObject removeFaceByFaceTokenList(String outerId, String faceSetToken, List<String> faceTokenList) {
        String faceTokens = faceTokenListToString(faceTokenList);
        return removeFaceByFaceToken(outerId, faceSetToken, faceTokens);
    }

    @Override
    public JSONObject removeFaceByUserId(String outerId, String faceSetToken, Long userId) {
        OaFace oaFace = new OaFace();
        oaFace.setUserId(userId);
        List<OaFace> oaFaceList = oaFaceMapper.selectByUserId(oaFace);
        List<String> faceTokenList = oaFaceList.stream().map(OaFace::getFaceToken).collect(Collectors.toList());
        return removeFaceByFaceTokenList(outerId, faceSetToken, faceTokenList);
    }

    @Override
    public void deleteFaceByPrimaryKey(OaFace oaFace) {
        if (oaFace.getFaceToken() == null) {
            throw new GlobalException("缺少faceToken，无法删除人脸");
        }
        oaFaceMapper.deleteByPrimaryKey(oaFace);
    }

    @Override
    public JSONObject setUserId(String faceToken, String userId) {
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        params.add("face_token", faceToken);
        params.add("user_id", userId);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.SET_USER_ID_URL, request, String.class));
    }

    @Override
    @SneakyThrows
    public JSONObject detect(byte[] imageBytes) {
        String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        params.add("image_base64", imageBase64);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.FACE_DETECT_URL, request, String.class));
    }

    @Override
    public JSONObject search(byte[] imageBytes, String outerId, String faceSetToken) {
        String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("api_key", FaceConstant.API_KEY);
        params.add("api_secret", FaceConstant.API_SECRET);
        params.add("image_base64", imageBase64);
        if (outerId != null) {
            params.add("outer_id", outerId);
        }
        if (!params.containsKey("outer_id")) {
            params.add("faceset_token", faceSetToken);
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, httpHeaders);
        return JSONObject.parseObject(restTemplate.postForObject(FaceConstant.FACE_SEARCH_URL, request, String.class));
    }

    @SneakyThrows
    @Override
    public byte[] processImage(MultipartFile file, int minWidth, int minHeight, int maxWidth, int maxHeight, int maxImageSize, String fileExtension) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedImage originalImage = ImageIO.read(file.getInputStream());
        Thumbnails.of(originalImage).scale(1.0).outputFormat(fileExtension).toOutputStream(baos);

        // 获取宽高
        int originalWidth = originalImage.getWidth();
        int originalHeight = originalImage.getHeight();

        int newWidth, newHeight;
        BufferedImage resizedImage = null;

        // 校验宽高是否大于最小尺寸
        if (originalWidth < minWidth || originalHeight < minHeight) {
            if (originalWidth > originalHeight) {
                newHeight = minHeight;
                newWidth = (int) (newHeight / (double) originalHeight * originalWidth);
            } else {
                newWidth = minWidth;
                newHeight = (int) (newWidth / (double) originalWidth * originalHeight);
            }
            Thumbnails.Builder<? extends BufferedImage> thumbnailBuilder = Thumbnails.of(originalImage);
            thumbnailBuilder.size(newWidth, newHeight).keepAspectRatio(true).outputFormat(fileExtension);
            resizedImage = thumbnailBuilder.asBufferedImage();
        }

        // 校验宽高是否小于最大尺寸
        if (originalWidth > maxWidth || originalHeight > maxHeight) {
            if (originalWidth > originalHeight) {
                newWidth = maxWidth;
                newHeight = (int) (newWidth / (double) originalWidth * originalHeight);
            } else {
                newHeight = maxHeight;
                newWidth = (int) (newHeight / (double) originalHeight * originalWidth);
            }
            Thumbnails.Builder<? extends BufferedImage> thumbnailBuilder = Thumbnails.of(originalImage);
            thumbnailBuilder.size(newWidth, newHeight).keepAspectRatio(true).outputFormat(fileExtension);
            resizedImage = thumbnailBuilder.asBufferedImage();
        }

        // 检测处理后的图片是否满足要求
        if (resizedImage != null) {
            int resizedWidth = resizedImage.getWidth();
            int resizedHeight = resizedImage.getHeight();
            if (resizedWidth < minWidth || resizedHeight < minHeight || resizedWidth > maxWidth || resizedHeight > maxHeight) {
                throw new GlobalException("图片像素尺寸应满足：最小48*48像素，最大4096*4096像素");
            }
            originalImage = resizedImage;
        }

        // 如果图片大小不小于2MB，尝试对图片进行压缩
        int quality = 95;
        while (baos.size() > maxImageSize && quality > 5) {
            baos.reset();
            Thumbnails.of(originalImage).outputQuality(quality / 100.0).toOutputStream(baos);
            quality -= 5;
        }

        if (baos.size() > maxImageSize) {
            throw new GlobalException("图片文件大小必须不超过2MB");
        }

        return baos.toByteArray();
    }

    private String faceTokenListToString(List<String> faceTokenList) {
        StringBuilder sb = new StringBuilder();
        for (String faceToken : faceTokenList) {
            if (sb.length() == 0) {
                sb.append(faceToken);
            } else {
                sb.append(",").append(faceToken);
            }
        }
        return sb.toString();
    }

    private List<String> faceTokensToList(String faceTokens) {
        String[] faceTokenArray = faceTokens.split(",");
        return Arrays.asList(faceTokenArray);
    }


//    public void test(){
//        Beanutils
//    }
}
